/**
 * 
 */
package com.radeonsys.jsfmock.junit3;

import static org.easymock.EasyMock.expect;
import static org.easymock.classextension.EasyMock.createNiceMock;
import static org.easymock.classextension.EasyMock.replay;
import static org.easymock.classextension.EasyMock.reset;
import static org.easymock.classextension.EasyMock.verify;

import java.util.AbstractMap;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.faces.application.Application;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.el.VariableResolver;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import junit.framework.TestCase;

import org.easymock.EasyMock;

import com.radeonsys.jsfmock.jsf.context.MockFacesContext;
import com.radeonsys.jsfmock.util.KeyValuePair;

/**
 * An abstract implementation of a JUnit 3 unit-test case which can be extended
 * for unit-testing JSF backing beans.
 * 
 * <p>This class specifically mocks the classes of the JSF framework and provides
 * access to the mocks to the subclasses. Some default expectations are already
 * setup on the mock objects, subclasses may consider implementing additional
 * expectations.</p>
 * 
 * <p>Expectations on mocked classes are set using {@link org.easymock.classextensions.EasyMock}
 * and expectations on mocked interfaces are set using {@link org.easymock.EasyMock}. It 
 * is strongly recommended that subclasses use the EasyMock API to set expectations on
 * the mock objects. However, if advanced mocking is required, subclasses could
 * consider using other mocking frameworks.</p>
 * 
 * @see junit.framework.TestCase
 * 
 * @see	javax.faces.application.Application;
 * @see	javax.faces.context.ExternalContext;
 * @see	javax.faces.context.FacesContext;
 * @see	javax.faces.el.VariableResolver;
 * 
 * @see javax.servlet.ServletContext;
 * @see javax.servlet.http.HttpServletRequest;
 * @see javax.servlet.http.HttpServletResponse;
 * @see javax.servlet.http.HttpSession;
 * 
 * @see org.easymock.classextensions.EasyMock
 * @see org.easymock.EasyMock
 * 
 * @author Ranganath Kini (ranganathkini at gmail dot com)
 * @version 1.0
 */
public abstract class AbstractBackingBeanTestCase extends TestCase {

	/**
	 * Stores a reference to the mocked {@link ServletContext}
	 */
	protected ServletContext mockServletContext;
	
	/**
	 * Stores a reference to the mocked {@link HttpServletRequest}
	 */
	protected HttpServletRequest mockHttpRequest;
	
	/**
	 * Stores a reference to the mocked {@link HttpServletResponse}
	 */
	protected HttpServletResponse mockHttpResponse;
	
	/**
	 * Stores a reference to the mocked {@link HttpSession}
	 */
	protected HttpSession mockHttpSession;
	
	/**
	 * Stores a reference to the mocked {@link FacesContext}
	 */
	protected FacesContext mockFacesContext;
	
	/**
	 * Stores a reference to the mocked {@link ExternalContext}
	 */
	protected ExternalContext mockExternalContext;
	
	/**
	 * Stores a reference to the mocked {@link Application}
	 */
	protected Application mockApplication;
	
	/**
	 * Stores a reference to the mocked {@link VariableResolver}
	 */
	protected VariableResolver mockVariableResolver;

	/** 
	 * Sets up test-fixtures before a specific test within this test-case is 
	 * executed.
	 * 
	 * <p>This method performs additional setup of the mock objects and sets-up
	 * default expectations on the mocked objects.</p>
	 * 
	 * <p><b>WARNING:</p> Sub-classes <b>must</b> call this method within their own <code>setUp()</code>
	 * method before setting up any additional expectations</p>
	 * 
	 * @see junit.framework.TestCase#setUp()
	 */
	protected void setUp() throws Exception {
		super.setUp();
		
		// setup the mock objects
		setupFrameworkMocks();
		
		// setup the default expectations on the mock objects
		setupDefaultExpectations();
	}
	
	/**
	 * Sets the state of the mocked objects into a playback state to prepare
	 * them before the test begins.
	 */
	protected final void replayFrameworkMocks() {
		EasyMock.replay(mockServletContext, mockHttpRequest, mockHttpResponse, mockHttpSession);
		replay(mockExternalContext, mockApplication, mockVariableResolver);
	}
	

	/* (non-Javadoc)
	 * @see junit.framework.TestCase#tearDown()
	 */
	protected void tearDown() throws Exception {
		resetFrameworkMocks();
		
		super.tearDown();
	}

	/**
	 * Initializes the mock objects for the JSF Framework, this is initialized
	 * as part of the setup process for the test case
	 */
	private void setupFrameworkMocks() {
		// setup the Servlet API classes
		mockServletContext = EasyMock.createNiceMock(ServletContext.class);
		mockHttpRequest = EasyMock.createNiceMock(HttpServletRequest.class);
		mockHttpResponse = EasyMock.createNiceMock(HttpServletResponse.class);
		mockHttpSession = EasyMock.createNiceMock(HttpSession.class);
		
		// setup the JSF Framework classes
		mockExternalContext = createNiceMock(ExternalContext.class);
		mockApplication = createNiceMock(Application.class);
		mockVariableResolver = createNiceMock(VariableResolver.class);
		
		mockFacesContext = MockFacesContext.buildMockFacesContext(
				mockExternalContext, mockApplication);
	}
	
	/**
	 * Sets up the basic default expectations on the mock objects of the JSF 
	 * Framework and Servlet API
	 */
	private void setupDefaultExpectations() {
		EasyMock.expect(mockHttpRequest.getSession()).andReturn(mockHttpSession).anyTimes();
		EasyMock.expect(mockHttpRequest.getSession(true)).andReturn(mockHttpSession).anyTimes();
		EasyMock.expect(mockHttpRequest.getSession(false)).andReturn(mockHttpSession).anyTimes();
		
		expect(mockExternalContext.getRequest()).andReturn(mockHttpRequest).anyTimes();
		expect(mockExternalContext.getResponse()).andReturn(mockHttpResponse).anyTimes();
		expect(mockExternalContext.getContext()).andReturn(mockServletContext).anyTimes();
		expect(mockExternalContext.getSession(true)).andReturn(mockHttpSession).anyTimes();
		expect(mockExternalContext.getSession(false)).andReturn(mockHttpSession).anyTimes();
	}
	
	/**
	 * @param requestParamMap
	 */
	protected final void setupRequestParameters(final Map<String, String[]> requestParamMap) {
		final Map<String, String[]> paramMap = Collections.unmodifiableMap(requestParamMap);
		
		final Map<String, String> requestMap = new AbstractMap<String, String>() {
			@Override
			public Set<Map.Entry<String, String>> entrySet() {
				final Set<Map.Entry<String, String>> result = new HashSet<Entry<String,String>>();
				for (String param : paramMap.keySet())
					result.add(new KeyValuePair<String, String>(param, paramMap.get(param)[0]));
				return result;
			}
		};
		
		EasyMock.expect(mockHttpRequest.getParameterMap()).andReturn(requestMap).anyTimes();
		EasyMock.expect(mockHttpRequest.getParameterNames()).andReturn(
				Collections.enumeration(paramMap.keySet())).anyTimes();
		
		for (String param : requestParamMap.keySet()) {
			EasyMock.expect(mockHttpRequest.getParameter(param)).andReturn(
					requestParamMap.get(param)[0]).anyTimes();
			EasyMock.expect(mockHttpRequest.getParameterValues(param)).andReturn(
					requestParamMap.get(param)).anyTimes();
		}
		
		expect(mockExternalContext.getRequestParameterNames()).andReturn(
				paramMap.keySet().iterator()).anyTimes();
		expect(mockExternalContext.getRequestMap()).andReturn(requestMap).anyTimes();
		expect(mockExternalContext.getRequestParameterValuesMap()).andReturn(
				paramMap).anyTimes();
	}
	
	/**
	 * @param requestParamMap
	 */
	protected final void setupRequestHeaders(final Map<String, String[]> requestHeaderMap) {
		final Map<String, String[]> hdrMap = Collections.unmodifiableMap(requestHeaderMap);
		
		final Map<String, String> headerMap = new AbstractMap<String, String>() {
			@Override
			public Set<Map.Entry<String, String>> entrySet() {
				final Set<Map.Entry<String, String>> result = new HashSet<Entry<String,String>>();
				for (String param : hdrMap.keySet())
					result.add(new KeyValuePair<String, String>(param, hdrMap.get(param)[0]));
				return result;
			}
		};
		
		EasyMock.expect(mockHttpRequest.getHeaderNames()).andReturn(
				Collections.enumeration(hdrMap.keySet())).anyTimes();
		
		for (String header : requestHeaderMap.keySet()) {
			EasyMock.expect(mockHttpRequest.getHeader(header)).andReturn(
					hdrMap.get(header)[0]).anyTimes();
			EasyMock.expect(mockHttpRequest.getHeaders(header)).andReturn(
					Collections.enumeration(Arrays.asList(hdrMap.get(header))))
					.anyTimes();
		}
		
		expect(mockExternalContext.getRequestHeaderMap()).andReturn(headerMap).anyTimes();
		expect(mockExternalContext.getRequestHeaderValuesMap()).andReturn(
				headerMap).anyTimes();
	}
	
	/**
	 * @param requestParamMap
	 */
	protected final void setupSessionAttributes(final Map<String, Object> sessionAttribMap) {
		final Map<String, Object> attribMap = Collections.unmodifiableMap(sessionAttribMap);
	
		EasyMock.expect(mockHttpSession.getAttributeNames()).andReturn(
				Collections.enumeration(attribMap.keySet())).anyTimes();
		
		for (String attrib : attribMap.keySet())
			EasyMock.expect(mockHttpSession.getAttribute(attrib)).andReturn(
					attribMap.get(attrib)).anyTimes();
		
		expect(mockExternalContext.getSessionMap()).andReturn(
				attribMap).anyTimes();
	}
	
	/**
	 * @param requestParamMap
	 */
	protected final void setupRequestAttributes(final Map<String, Object> requestAttribMap) {
		final Map<String, Object> attribMap = Collections.unmodifiableMap(requestAttribMap);
	
		EasyMock.expect(mockHttpRequest.getAttributeNames()).andReturn(
				Collections.enumeration(attribMap.keySet())).anyTimes();
		
		for (String attrib : attribMap.keySet())
			EasyMock.expect(mockHttpRequest.getAttribute(attrib)).andReturn(
					attribMap.get(attrib)).anyTimes();
		
		expect(mockExternalContext.getRequestMap()).andReturn(
				attribMap).anyTimes();
	}
	
	/**
	 * @param applicationAttribMap
	 */
	protected final void setupApplicationAttributes(final Map<String, Object> applicationAttribMap) {
		final Map<String, Object> attribMap = Collections.unmodifiableMap(applicationAttribMap);
	
		EasyMock.expect(mockServletContext.getAttributeNames()).andReturn(
				Collections.enumeration(attribMap.keySet())).anyTimes();
		
		for (String attrib : attribMap.keySet())
			EasyMock.expect(mockServletContext.getAttribute(attrib)).andReturn(
					attribMap.get(attrib)).anyTimes();
		
		expect(mockExternalContext.getApplicationMap()).andReturn(
				attribMap).anyTimes();
	}
	
	/**
	 * Verifies the calls on the mocks associated with the JSF framework
	 */
	protected final void verifyFrameworkMocks() {
		EasyMock.verify(mockServletContext, mockHttpRequest, mockHttpResponse, mockHttpSession);
		verify(mockExternalContext, mockApplication, mockVariableResolver);
	}
	
	/**
	 * Resets the mocks associated with the JSF framework
	 */
	private void resetFrameworkMocks() {
		EasyMock.reset(mockServletContext, mockHttpRequest, mockHttpResponse, mockHttpSession);
		reset(mockExternalContext, mockApplication, mockVariableResolver);
		mockFacesContext = null;
	}

}
